package cz.cuni.amis.pogamut.episodic.decisions;

import cz.cuni.amis.pogamut.episodic.memory.Affordance;
import cz.cuni.amis.pogamut.episodic.memory.IdGenerator;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * Name of this class can be a little misleading because <code>DecisionTree</code>
 * is actually a forest constiting of several trees. Roots of these trees
 * are all possible agent's top-level goals. It is a copy of decision plans
 * from pogamut end part of the project.
 * <p>
 * These trees have to be constructed before any information can be added to
 * agent's memory structures.
 * <p>
 * Each action agent performs is representing by equivalent node in decision
 * tree. This node is located through this class by locating the correct
 * top-level goal first and by going through tree structure until the specified
 * node is located. Each time agent performs an action it is added to memory
 * by providing trace of such nodes that lead to a node representing atomic
 * action that was just executed.
 *
 * @author Michal Cermak
 * @see Node
 */
public class DecisionTree implements Serializable {
    /**
     * Determines if a de-serialized file is compatible with this class.
     *
     * Maintainers must change this value if and only if the new version
     * of this class is not compatible with old versions. See Sun docs
     * for <a href=http://java.sun.com/products/jdk/1.1/docs/guide
     * /serialization/spec/version.doc.html> details. </a>
     *
     * Not necessary to include in first version of the class, but
     * included here as a reminder of its importance.
     */
    private static final long serialVersionUID = 1L;

    /**
     * Map of all possible top-level goals agent can have. They are indexed
     * by their name (so there cannot be two top-level goals with identical
     * names). Through here it is possible to get to any node in the decision
     * trees.
     */
    public HashMap<String, Intention> topLevelGoals = new HashMap<String, Intention>();

    public int numberOfAtomicActions = 0;

    /**
     * This number is <strong>without</strong> atomic actions. It only includes
     * intentions and actions.
     */
    public int numberOfNodes = 0;

    public ArrayList<Integer> branchFactor = new ArrayList<Integer>();

    public ArrayList<Integer> getBranchFactor() {
        for (Intention i : topLevelGoals.values()) {
            branchFactor.add(i.getPossibleSubTrees());
        }

        return branchFactor;
    }

    /**
     * This method is used in schemabag when the trace specified by a list
     * of node names is not enough and other information on nodes are needed.
     * It transforms the trace of node names into the equivalent trace of
     * references to node themselves.
     *
     * @param trace Ordered list of names of nodes beginning with a name
     * of one top-level goal a next item being always a name of one child node
     * of previous node.
     * @return  Returns the equivalent trace of references to the <code>Node</code>
     * structures that was specified by the names in parameter.
     * <p>
     * If there is non-existent node name in the middle of the list, method
     * returns references to the nodes named by previous correct entries only.
     */
    public ArrayList<Node> getTrace(ArrayList<String> trace) {
        ArrayList<Node> list = new ArrayList<Node>();

        if (trace.isEmpty()) return list;
        Intention i = topLevelGoals.get(trace.get(0));
        if (i != null)
            list.add(i);
        Action a;
        int index = 1;
        while (index < trace.size()) {
            a = (Action) i.getSubNode(trace.get(index));
            index++;
            if (a != null) {
                list.add(a);
                if (index < trace.size()) {
                    i = (Intention) a.getSubIntention(trace.get(index));
                    index++;
                    if (i != null) {
                        list.add(i);
                    } else break;
                }
            } else break;
        }

        return list;
    }

    /**
     * In case actions performed were not included in xmls. This method creates
     * nodes in decision tree, representing these actions.
     *
     * @param trace Ordered list of names of nodes beginning with a name
     * of one top-level goal a next item being always a name of one child node
     * of previous node.
     * @return  Return true if a new node was added, false otherwise.
     */
    public boolean ensureNodes(String nodeName, ArrayList<String> _trace, ArrayList<Affordance> affordances, IdGenerator gen) throws Exception {
        ArrayList<String> trace = new ArrayList<String>(_trace); // Duplicate to make it immutable

        // Invariably:
        // Traces come in alternating pairs Action,Intention
        // if nodeName is Action or Intention, it is also the last item in the trace
        // atomicAction can only be the child of Intention
        boolean updated = false;
        Action a = null;
        String str = trace.get(0);
        Intention i = topLevelGoals.get(str);
        if (i == null) {
            i = new Intention(str, 0);
            i.setId(gen.getNewId());
            topLevelGoals.put(str, i);
        }
        if ((trace.size() != 1)) {

            int index = 1;
            while (index < trace.size()) {
                str = trace.get(index);
                a = (Action) i.getSubNode(str);
                if (a == null) {
                    a = new Action(str, 0);
                    a.setId(gen.getNewId());
                    i.addSubNode(a);
                    updated = true;
                }
                if (index == trace.size()-1) {
                    break;
                }

                index++;
                str = trace.get(index);
                if (index == trace.size()) break;
                i = a.getSubIntention(str);
                if (i == null) {
                    i = new Intention(str, 0);
                    i.setId(gen.getNewId());
                    a.addSubNode(i);
                    a = null;
                    updated = true;
                }
                if (index == trace.size()-1) {
                    break;
                }
                index++;
            }
        }

        if (str.equals(nodeName)) {
            // Node was either an Action or Intention, nothing else needed.
            return true;
        }

        if (a == null) {
            throw new Exception("Attempting to add atomicAction as a child of Intention node.");
        } else {
            if (a.getAtomicAction(nodeName) == null) {
                AtomicAction aa = new AtomicAction(nodeName, 0);
                aa.setId(gen.getNewId());
                a.addAtomicAction(aa);
                updated = true;
            }
        }

        return updated;
    }
}
